﻿using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Reflection;
using Devonline.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;

namespace Devonline.Database.PostgreSQL;

public static class ServiceExtensions
{
    /// <summary>
    /// 注册数据库上下文
    /// </summary>
    /// <param name="services">依赖注入服务容器</param>
    /// <param name="appSetting">基础配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddDbContext<TDbContext>(this IServiceCollection services, AppSetting appSetting) where TDbContext : DbContext
    {
        if (!string.IsNullOrWhiteSpace(appSetting.ApplicationDbContext))
        {
            var assembly = appSetting.MigrationsAssembly ?? Assembly.GetCallingAssembly().FullName;
            services.AddDbContext<TDbContext>(builder => builder.UseNpgsql(appSetting.ApplicationDbContext, options => options.MigrationsAssembly(assembly)), appSetting.ServiceLifetime);
        }

        return services;
    }
    /// <summary>
    /// 注册数据库上下文
    /// </summary>
    /// <param name="services">依赖注入服务容器</param>
    /// <param name="connectionString">数据库连接字符串</param>
    /// <param name="migrationsAssembly">数据迁移作用的程序集</param>
    /// <param name="contextLifetime">数据库上下文的生命周期</param>
    /// <returns></returns>
    public static IServiceCollection AddDbContext<TDbContext>(this IServiceCollection services, string? connectionString = default, string? migrationsAssembly = default, ServiceLifetime contextLifetime = ServiceLifetime.Scoped) where TDbContext : DbContext
    {
        if (!string.IsNullOrWhiteSpace(connectionString))
        {
            var assembly = migrationsAssembly ?? Assembly.GetCallingAssembly().FullName;
            services.AddDbContext<TDbContext>(builder => builder.UseNpgsql(connectionString, options => options.MigrationsAssembly(assembly)), contextLifetime);
        }

        return services;
    }

    /// <summary>
    /// 注册数据库上下文创建对象
    /// </summary>
    /// <param name="builder">依赖注入服务容器</param>
    /// <param name="connectionString">数据库连接字符串</param>
    /// <param name="migrationsAssembly">数据迁移作用的程序集</param>
    /// <returns></returns>
    public static DbContextOptionsBuilder Build(this DbContextOptionsBuilder builder, string? connectionString = default, string? migrationsAssembly = default)
    {
        if (!string.IsNullOrWhiteSpace(connectionString))
        {
            var assembly = migrationsAssembly ?? Assembly.GetCallingAssembly().FullName;
            return builder.UseNpgsql(connectionString, options => options.MigrationsAssembly(assembly));
        }

        return builder;
    }

    /// <summary>
    /// 批量插入pgsql数据库
    /// </summary>
    /// <typeparam name="T">要写入的数据类型</typeparam>
    /// <param name="data">要写入的数据</param>
    /// <returns></returns>
    public static async Task<ulong> InsertsAsync<T>(this DbContext context, IEnumerable<T> data)
    {
        if (data is null || !data.Any())
        {
            return 0;
        }

        ulong result = 0;
        var connection = context.Database.GetDbConnection();
        if (connection is NpgsqlConnection npgsqlConnection)
        {
            if (npgsqlConnection.State == ConnectionState.Closed)
            {
                await npgsqlConnection.OpenAsync();
            }

            var type = typeof(T);
            var propertyInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => (x.PropertyType.IsValueType || x.PropertyType == typeof(string)) && !x.HasAttribute<NotMappedAttribute>());
            var columns = propertyInfos.Select(x => x.GetColumnName()).ToString(",", "\"{0}\"");
            var copySql = $"COPY {type.GetTableName()} ({columns}) FROM STDIN (FORMAT BINARY)";

            using var writer = npgsqlConnection.BeginBinaryImport(copySql);
            foreach (var t in data)
            {
                await writer.StartRowAsync();
                foreach (var propertyInfo in propertyInfos)
                {
                    var value = t.GetSqlStringValue<T>(propertyInfo);
                    await writer.WriteAsync(value);
                }
            }

            result = await writer.CompleteAsync();
        }

        return result;
    }
}